<?php defined('DML') or die("Access locked");

// websun 0.1.12

class Template{
	
	public $vars;
	public $templates_root_dir; // templates_root_dir указывать без закрывающего слэша!
	public $templates_current_dir;
	
	
	function __construct($vars = array(), $templates_root = FALSE) {
		$this->vars = $vars; 
		if ($templates_root) // корневой каталог шаблонов
			$this->templates_root_dir = $templates_root;
		else // если не указан, то принимается текущий каталог файла шаблонизатора
			$this->templates_root_dir = dirname(__FILE__); 
		
		$this->templates_current_dir = $this->templates_root_dir . '/';
	}
	
	function parse_template($template) {
		$template = preg_replace('/ \\/\* (.*?) \*\\/ /sx', '', $template); /**ПЕРЕПИСАТЬ ПО JEFFREY FRIEDL'У !!!**/
		
		$template = str_replace('\\\\', "\x01", $template); 	// убираем двойные слэши
		$template = str_replace('\*', "\x02", $template);	// и экранированные звездочки
		
		$template = preg_replace_callback( // дописывающие модули
			'/
			{\*
			&(\w+)
			(?P<args>\([^*]*\))?
			\*}
			/x', 
			array($this, 'addvars'), 
			$template
			);
		
		$template = $this->find_and_parse_cycle($template);
		
		$template = $this->find_and_parse_if($template);
		
		$template = preg_replace_callback( // переменные, шаблоны и модули
			'/
			{\*
			(.*?)
			\*}
			/x', 
			array($this, 'parse_vars_templates_modules'), 
			$template
			);
		
		$template = str_replace("\x01", '\\\\', $template); // возвращаем двойные слэши обратно
		$template = str_replace("\x02", '*', $template); // а звездочки - уже без экранирования 
		
		return $template;
	}
	
	function addvars($matches) {
		$module_name = 'module_'.$matches[1];
		# ДОБАВИТЬ КЛАССЫ ПОТОМ
		$args = (isset($matches['args'])) 
			? explode(',', substr($matches['args'], 1, -1) ) // убираем скобки
			: array();
		$this->vars = array_merge(
			$this->vars, 
			call_user_func_array($module_name, $args)
			); // call_user_func_array быстрее, чем call_user_func 
		return '';
	}
	
	function var_value($string) {
		if (substr($string, 0, 1) == '$') { // глобальная
			$string = substr($string, 1);
			$value = $GLOBALS;
		}
		else $value = $this->vars;
		$rawkeys = explode('.', $string);
		$keys = array();
		foreach ($rawkeys as $v) { if ($v !== '') $keys[] = $v; }
		// array_filter() использовать не получается, 
		// т.к. числовой индекс 0 она тоже считает за FALSE и убирает
		// поэтому нужно сравнение с учетом типа
		
		// пустая строка указывает на корневой массив
		foreach ($keys as $k) {
			if (is_array($value) AND isset($value[$k]) ) $value = $value[$k]; 
			else { $value = NULL; break; }
		}
		
		return $value;
	}
	
	function find_and_parse_cycle($template) {
		// пришлось делать специальную функцию, чтобы реализовать рекурсию
		return preg_replace_callback(
			'/
			{%\* ([-\w$.]*) \*}
			(.*?)
			{\* \1 \*%}
			/sx', 
			array($this, 'parse_cycle'), 
			$template
			);
	}
	
	function parse_cycle($matches) {
		$array_name = $matches[1];
		$array = $this->var_value($array_name);
		if ( ! is_array($array) ) return FALSE;
		$parsed = '';
		$dot = ( $array_name != '' AND $array_name != '$' ) ? '.' : '';
		foreach ($array as $key => $value) {
			$parsed .= str_replace("*$array_name:", "*$array_name$dot$key.", $matches[2]);
		}
		$parsed = $this->find_and_parse_cycle($parsed);
		return $parsed;
	}
	
	function find_and_parse_if($template) {
		return preg_replace_callback( 
			'/
			{ (\?!?) \* 
			( 
				(?P<varname> [-\w$.]* ) 
				( (?P<sign>[=<>])(?P<value>[^*]*) )? 
			)
			\* }
			(?P<body> .*? )
			{\* \2 \* \1}
			/sx',
			array($this, 'parse_if'), 
			$template
			);
	}
	
	function parse_if($matches) {
		$varvalue = $this->var_value($matches['varname']);
		if (isset($matches['sign']) AND $matches['sign']) {
			switch($matches['sign']) {
				case '=': $varcheck = ($varvalue == $matches['value']); break;
				case '>': $varcheck = ($varvalue > $matches['value']); break;
				case '<': $varcheck = ($varvalue < $matches['value']); break;
			}
		}
		else $varcheck = ($varvalue == TRUE);
		$result = ($matches[1] == '?') ? $varcheck : !$varcheck; 
		$parsed_if = ($result) ? $this->find_and_parse_if($matches['body']) : '';
		return $parsed_if;
	}
	
	function parse_vars_templates_modules($matches) {
		// тут обрабатываем сразу всё - и переменные, и шаблоны, и модули
		$work = $matches[1]; 
		$work = trim($work); // убираем пробелы по краям
		if (substr($work, 0, 1) == '@') { // модуль {* @name(arg1,arg2) | template *}
			$p = '/
				^
				(?P<module>[^(|]++)
				(?: \( (?P<argstr>[^)]*+) \) \s* )?
				(?: \| \s* (?P<tpl>.++) )? # это уже до конца
				$
				/x';
			if (preg_match( $p, substr($work, 1), $m) ) {
				$tmp = $this->get_var_or_string($m['module']);
				$module_name = (strpos($tmp, 'module_') === 0)
					? $tmp
					: "module_$tmp" ;
				$args = ( isset($m['argstr']) ) 
					? array_map( array($this, 'get_var_or_string'), explode(',', $m['argstr']) )
					: array();
				$subvars = call_user_func_array($module_name, $args);
				if ( isset($m['tpl']) ) {
					$tplname = $this->get_var_or_string($m['tpl']);
					$html = $this->parse_child_template($tplname, $subvars);
				}
				else $html = $subvars; // шаблон не указан => модуль возвращает строку
			}
			else $html = ''; // вызов модуля сделан некорректно
		}
		elseif (substr($work, 0, 1) == '+') { // шаблон - {* +*vars_var*|*tpl_var* *}
			$html = '';
			$parts = explode( '|', substr($work, 1) ); // плюс убрали
			$parts = array_map('trim', $parts); // убираем пробелы по краям
			if ( !isset($parts[1]) ) { 	// если нет разделителя (|) - значит, 
							// передали только имя шаблона +template
				$tplname = $this->get_var_or_string($parts[0]);
				$html = $this->parse_child_template($tplname, $this->vars); // работаем с корневым массивом vars
			}
			else {
				$tplname = $this->get_var_or_string($parts[1]); 
				$varname_string = substr($parts[0], 1, -1); // убираем звездочки
				// {* +*vars* | шаблон *} - простая передача переменной шаблону
				// {* +*?vars* | шаблон *} - подключение шаблона только в случае, если vars == TRUE
				// {* +*%vars* | шаблон *} - подключение шаблона не для самого vars, а для каждого его дочернего элемента 
				$indicator = substr($varname_string, 0, 1);
				if ($indicator == '?') { 
					if ( $subvars = $this->var_value( substr($varname_string, 1) ) )
						$html = $this->parse_child_template($tplname, $subvars);
				}
				elseif ($indicator == '%') {
					if ( $subvars = $this->var_value( substr($varname_string, 1) ) ) {
						foreach ( $subvars as $row ) { 
							$html .= $this->parse_child_template($tplname, $row);
						}
					}
				}
				else {
					$subvars = $this->var_value( $varname_string );
					$html = $this->parse_child_template($tplname, $subvars);
				}
			}
		}
		elseif (substr($work, 0, 1) == '=') { // константа
			$C = substr($work, 1);
			$html = (defined($C)) ? constant($C) : '';
		}
		else $html = $this->var_value($work); // переменная
		return $html;
	}
	
	function parse_child_template($subtplname, $subvars) {
		$c = __CLASS__; // нужен объект этого же класса - делаем
		$subobject = new $c($subvars, $this->templates_root_dir);
		$subobject->templates_current_dir = pathinfo( $this->template_real_path($subtplname), PATHINFO_DIRNAME ) . '/';
		// важно получать subtemplate именно после установки templates_current_dir дочернего объекта
		$subtemplate = $this->get_template($subtplname); 
		$html = $subobject->parse_template($subtemplate);
		return $html;
	}
	
	function get_var_or_string($str) {
		return ( substr($str, 0, 1) == '*' AND substr($str, -1) == '*')
			? $this->var_value( substr($str, 1, -1) ) 
			: $str // если вокруг есть звездочки - значит, перменная 
			; // нет звездочек - значит, обычная строка-литерал
	}
	
	function get_template($tpl) {
		if (!$tpl) return FALSE;
		$tpl_real_path = $this->template_real_path($tpl);
		return rtrim(file_get_contents($tpl_real_path), "\r\n"); 
		// (убираем перенос строки, присутствующий в конце любого файла)
	}
	
	
	function template_real_path($tpl) {
		// функция определяет реальный путь к шаблону в файловой системе
		// первый символ пути к шаблону определяет тип пути 
		// если в начале адреса есть / - интерпретируем как абсолютный путь ФС
		// если второй символ пути - двоеточие (путь вида C:/ - Windows) - 
		// также интепретируем как абсолютный путь ФС
		// если есть ^ - отталкиваемся от $templates_root_dir
		// если $ - от $_SERVER[DOCUMENT_ROOT]
		// во всех остальных случаях отталкиваемся от каталога текущего шаблона - templates_current_dir
		$dir_indicator = substr($tpl, 0, 1);
		
		$adjust_tpl_path = TRUE;
		
		if ($dir_indicator == '^') $dir = $this->templates_root_dir;
		elseif ($dir_indicator == '$') $dir = $_SERVER['DOCUMENT_ROOT'];
		elseif ($dir_indicator == '/') { $dir = ''; $adjust_tpl_path = FALSE; } // абсолютный путь для ФС 
		else {
			if (substr($tpl, 1, 1) == ':') // Windows - указан абсолютный путь - вида С:/...
				$dir = '';
			else  
				$dir = $this->templates_current_dir;  
			
			$adjust_tpl_path = FALSE; // в обоих случаях строку к пути менять не надо
		}
		
		if ($adjust_tpl_path) $tpl = substr($tpl, 1);
		
		$tpl_real_path = $dir . $tpl;
		
		return $tpl_real_path;
	}
}


function ParseTamplatePath($data, $template_path, $templates_root_dir = FALSE) {
	// функция-обёртка для быстрого вызова класса
	// принимает шаблон в виде пути к нему
	$W = new Template($data, $templates_root_dir);
	$tpl = $W->get_template($template_path);
	$string = $W->parse_template($tpl);
	return $string;
}


function ParseTemplateString($data, $template_code, $templates_root_dir = FALSE) {
	// функция-обёртка для быстрого вызова класса
	// принимает шаблон непосредственно в виде кода
	$W = new Template($data, $templates_root_dir);
	$string = $W->parse_template($template_code);
	return $string;
}
?>